/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <pony/lcd/gc9a01.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_config.h>
#include <x_module.h>
#include <x_symbol.h>

#include <k_errno.h>

#include <hal_spi.h>
#include <hal_gpio.h>
#include <hal_delay.h>

/**
 * RES  PB12
 * DC   PB13
 * CS   PB14
 * BLK  PB15
 */

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/

typedef struct gc9a01_dev {
    hal_lcd_t dev;
    hal_spi_t *spi;
} gc9a01_dev_t;

/* macro ---------------------------------------------------------------------*/

#define LCD_RES_Clr() hal_gpio_write(&gpio_res, 0)
#define LCD_RES_Set() hal_gpio_write(&gpio_res, 1)

#define LCD_DC_Clr() hal_gpio_write(&gpio_dc, 0)
#define LCD_DC_Set() hal_gpio_write(&gpio_dc, 1)

#define LCD_CS_Clr() hal_gpio_write(&gpio_cs, 0)
#define LCD_CS_Set() hal_gpio_write(&gpio_cs, 1)

#define LCD_BLK_Clr() hal_gpio_write(&gpio_blk, 0)
#define LCD_BLK_Set() hal_gpio_write(&gpio_blk, 1)

/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static gc9a01_dev_t gc9a01;

static hal_gpio_t gpio_res;
static hal_gpio_t gpio_dc;
static hal_gpio_t gpio_cs;
static hal_gpio_t gpio_blk;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static int lcd_write_byte(uint8_t dat)
{
    return hal_spi_write(gc9a01.spi, &dat, 1, 100);
}

/* double bytes */
static void lcd_write_word(uint32_t dat)
{
    lcd_write_byte(dat >> 8);
    lcd_write_byte(dat);
}

static void lcd_write_reg(uint8_t dat)
{
    LCD_DC_Clr();
    lcd_write_byte(dat);
    LCD_DC_Set();
}

static void lcd_set_address(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2)
{
    lcd_write_reg(GC9A01_CASET);
    lcd_write_word(x1);
    lcd_write_word(x2);
    lcd_write_reg(GC9A01_RASET);
    lcd_write_word(y1);
    lcd_write_word(y2);
    lcd_write_reg(GC9A01_RAMWR);
}

static int __gc9a01_gpio_init(void)
{
    int ret;

    gpio_res.gpio = CONFIG_LCD_GC9A01_RES_PORT;
    gpio_res.pin = CONFIG_LCD_GC9A01_RES_PIN;
    gpio_res.mode = HAL_GPIO_MODE_OUTPUT_PP;
    gpio_res.pull = HAL_GPIO_PULLUP;
    if ((ret = hal_gpio_init(&gpio_res)) != 0) {
        return ret;
    }

    gpio_dc.gpio = CONFIG_LCD_GC9A01_DC_PORT;
    gpio_dc.pin = CONFIG_LCD_GC9A01_DC_PIN;
    gpio_dc.mode = HAL_GPIO_MODE_OUTPUT_PP;
    gpio_dc.pull = HAL_GPIO_PULLUP;
    if ((ret = hal_gpio_init(&gpio_dc)) != 0) {
        return ret;
    }

    gpio_cs.gpio = CONFIG_LCD_GC9A01_CS_PORT;
    gpio_cs.pin = CONFIG_LCD_GC9A01_CS_PIN;
    gpio_cs.mode = HAL_GPIO_MODE_OUTPUT_PP;
    gpio_cs.pull = HAL_GPIO_PULLUP;
    if ((ret = hal_gpio_init(&gpio_cs)) != 0) {
        return ret;
    }

    gpio_blk.gpio = CONFIG_LCD_GC9A01_BLK_PORT;
    gpio_blk.pin = CONFIG_LCD_GC9A01_BLK_PIN;
    gpio_blk.mode = HAL_GPIO_MODE_OUTPUT_PP;
    gpio_blk.pull = HAL_GPIO_PULLUP;
    if ((ret = hal_gpio_init(&gpio_blk)) != 0) {
        return ret;
    }

    return 0;
}

static int __gc9a01_lcd_init(hal_lcd_t *lcd)
{
    LCD_RES_Clr();
    DELAY_MS(20);
    LCD_RES_Set();
    DELAY_MS(20);
    LCD_BLK_Set();

    // GC9A01 + HSD1.09 init code
    lcd_write_reg(GC9A01_ENIREG1);
    lcd_write_reg(GC9A01_ENIREG2);

    lcd_write_reg(0xEB);
    lcd_write_byte(0x14);

    lcd_write_reg(0x84);
    lcd_write_byte(0x40);

    lcd_write_reg(0x88);
    lcd_write_byte(0x0A);

    lcd_write_reg(0x89);
    lcd_write_byte(0x21);

    lcd_write_reg(0x8A);
    lcd_write_byte(0x00);

    lcd_write_reg(0x8B);
    lcd_write_byte(0x80);

    lcd_write_reg(0x8C);
    lcd_write_byte(0x01);

    lcd_write_reg(0x8D);
    lcd_write_byte(0x03);

    lcd_write_reg(0x8F);
    lcd_write_byte(0xFF);

    lcd_write_reg(0xB6);
    lcd_write_byte(0x00);
    lcd_write_byte(0x60);

    lcd_write_reg(GC9A01_MADCTL);
    lcd_write_byte(0x88);

    lcd_write_reg(GC9A01_COLMOD);
    lcd_write_byte(0x05);

    lcd_write_reg(0x90);
    lcd_write_byte(0x08);
    lcd_write_byte(0x08);
    lcd_write_byte(0x08);
    lcd_write_byte(0x08);

    lcd_write_reg(0xBD);
    lcd_write_byte(0x06);

    lcd_write_reg(0xBC);
    lcd_write_byte(0x00);

    lcd_write_reg(0xFF);
    lcd_write_byte(0x60);
    lcd_write_byte(0x01);
    lcd_write_byte(0x04);

    lcd_write_reg(0xC3);
    lcd_write_byte(0x2F);

    lcd_write_reg(0xC4);
    lcd_write_byte(0x2F);

    lcd_write_reg(0xC9);
    lcd_write_byte(0x25);

    lcd_write_reg(0xBE);
    lcd_write_byte(0x11);

    lcd_write_reg(0xE1);
    lcd_write_byte(0x10);
    lcd_write_byte(0x0E);

    lcd_write_reg(0xDF);
    lcd_write_byte(0x21);
    lcd_write_byte(0x10);
    lcd_write_byte(0x02);

    lcd_write_reg(0xF0);
    lcd_write_byte(0x49);
    lcd_write_byte(0x0e);
    lcd_write_byte(0x09);
    lcd_write_byte(0x09);
    lcd_write_byte(0x25);
    lcd_write_byte(0x2e);

    lcd_write_reg(0xF1);
    lcd_write_byte(0x44);
    lcd_write_byte(0x70);
    lcd_write_byte(0x73);
    lcd_write_byte(0x2F);
    lcd_write_byte(0x30);
    lcd_write_byte(0x6F);

    lcd_write_reg(0xF2);
    lcd_write_byte(0x49);
    lcd_write_byte(0x0e);
    lcd_write_byte(0x09);
    lcd_write_byte(0x09);
    lcd_write_byte(0x25);
    lcd_write_byte(0x2e);

    lcd_write_reg(0xF3);
    lcd_write_byte(0x44);
    lcd_write_byte(0x70);
    lcd_write_byte(0x73);
    lcd_write_byte(0x2F);
    lcd_write_byte(0x30);
    lcd_write_byte(0x6F);

    lcd_write_reg(0xED);
    lcd_write_byte(0x1B);
    lcd_write_byte(0x8B);

    lcd_write_reg(0xAE);
    lcd_write_byte(0x77);

    lcd_write_reg(0xCD);
    lcd_write_byte(0x63);

    lcd_write_reg(0xAC);
    lcd_write_byte(0x27);

    lcd_write_reg(0x70);
    lcd_write_byte(0x07);
    lcd_write_byte(0x07);
    lcd_write_byte(0x04);
    lcd_write_byte(0x06);
    lcd_write_byte(0x0F);
    lcd_write_byte(0x09);
    lcd_write_byte(0x07);
    lcd_write_byte(0x08);
    lcd_write_byte(0x03);

    lcd_write_reg(0xE8);
    lcd_write_byte(0x24);

    lcd_write_reg(0x60);
    lcd_write_byte(0x38);
    lcd_write_byte(0x0B);
    lcd_write_byte(0x6D);
    lcd_write_byte(0x6D);
    lcd_write_byte(0x39);
    lcd_write_byte(0xF0);
    lcd_write_byte(0x6D);
    lcd_write_byte(0x6D);

    lcd_write_reg(0x61);
    lcd_write_byte(0x38);
    lcd_write_byte(0xF4);
    lcd_write_byte(0x6D);
    lcd_write_byte(0x6D);
    lcd_write_byte(0x38);
    lcd_write_byte(0xF7);
    lcd_write_byte(0x6D);
    lcd_write_byte(0x6D);

    lcd_write_reg(0x62);
    lcd_write_byte(0x38);
    lcd_write_byte(0x0D);
    lcd_write_byte(0x71);
    lcd_write_byte(0xED);
    lcd_write_byte(0x70);
    lcd_write_byte(0x70);
    lcd_write_byte(0x38);
    lcd_write_byte(0x0F);
    lcd_write_byte(0x71);
    lcd_write_byte(0xEF);
    lcd_write_byte(0x70);
    lcd_write_byte(0x70);

    lcd_write_reg(0x63);
    lcd_write_byte(0x38);
    lcd_write_byte(0x11);
    lcd_write_byte(0x71);
    lcd_write_byte(0xF1);
    lcd_write_byte(0x70);
    lcd_write_byte(0x70);
    lcd_write_byte(0x38);
    lcd_write_byte(0x13);
    lcd_write_byte(0x71);
    lcd_write_byte(0xF3);
    lcd_write_byte(0x70);
    lcd_write_byte(0x70);

    lcd_write_reg(0x64);
    lcd_write_byte(0x28);
    lcd_write_byte(0x29);
    lcd_write_byte(0xF1);
    lcd_write_byte(0x01);
    lcd_write_byte(0xF1);
    lcd_write_byte(0x00);
    lcd_write_byte(0x1a);

    lcd_write_reg(0x66);
    lcd_write_byte(0x3C);
    lcd_write_byte(0x00);
    lcd_write_byte(0x98);
    lcd_write_byte(0x10);
    lcd_write_byte(0x32);
    lcd_write_byte(0x45);
    lcd_write_byte(0x01);
    lcd_write_byte(0x00);
    lcd_write_byte(0x00);
    lcd_write_byte(0x00);

    lcd_write_reg(0x67);
    lcd_write_byte(0x00);
    lcd_write_byte(0x3C);
    lcd_write_byte(0x00);
    lcd_write_byte(0x00);
    lcd_write_byte(0x00);
    lcd_write_byte(0x10);
    lcd_write_byte(0x54);
    lcd_write_byte(0x67);
    lcd_write_byte(0x45);
    lcd_write_byte(0xcd);

    lcd_write_reg(0x74);
    lcd_write_byte(0x10);
    lcd_write_byte(0x85);
    lcd_write_byte(0x80);
    lcd_write_byte(0x00);
    lcd_write_byte(0x00);
    lcd_write_byte(0x4E);
    lcd_write_byte(0x00);

    lcd_write_reg(0x98);
    lcd_write_byte(0x3e);
    lcd_write_byte(0x07);

    lcd_write_reg(0x99);
    lcd_write_byte(0x3e);
    lcd_write_byte(0x07);

    lcd_write_reg(GC9A01_TEON);
    lcd_write_reg(GC9A01_INVON);
    DELAY_MS(120);
    //--------end gamma setting--------------//

    lcd_write_reg(GC9A01_SLPOUT);
    DELAY_MS(320);
    lcd_write_reg(GC9A01_DISPON);
    DELAY_MS(120);
    lcd_write_reg(GC9A01_RAMWR);

    return 0;
}

static int __gc9a01_lcd_setpixel(hal_lcd_t *lcd, uint32_t x, uint32_t y, uint32_t color)
{
    lcd_set_address(x, y, x, y);
    lcd_write_word(color);
    return 0;
}

static int __gc9a01_lcd_ioctl(hal_lcd_t *lcd, int cmd, va_list valist)
{
    uint32_t value = va_arg(valist, uint32_t);

    switch (cmd) {
    case HAL_LCD_CMD_BLK_CTRL:
        hal_gpio_write(&gpio_blk, value);
        break;
    case HAL_LCD_CMD_BLK_TOGGLE:
        hal_gpio_toggle(&gpio_blk);
        break;
    case HAL_LCD_CMD_RESET_CTRL:
        hal_gpio_write(&gpio_res, value);
        break;
    default:
        return -ENOSYS;
    }

    return 0;
}

static const hal_lcd_methods_t gc9a01_dev_methods = {
    .init = __gc9a01_lcd_init,
    .setpixel = __gc9a01_lcd_setpixel,
    .ioctl = __gc9a01_lcd_ioctl,
};

static int gc9a01_init(void)
{
    int ret;

    if ((ret = __gc9a01_gpio_init()) != 0) {
        return ret;
    }

    hal_spi_t *spi = hal_spi_open(CONFIG_LCD_GC9A01_SPI_BUS);
    if (spi == NULL) {
        return -EPERM;
    }
    gc9a01.spi = spi;

    return hal_lcd_register(&gc9a01.dev, "gc9a01", &gc9a01_dev_methods, 0);
}

MODULE_INIT(driver, gc9a01_init);
